package com.xjd.transport.impl;

import java.util.ArrayList;
import java.util.List;

import com.xjd.transport.Channel;
import com.xjd.transport.Future;
import com.xjd.transport.FutureListener;
import com.xjd.transport.TransportStates;
import com.xjd.transport.work.Work;

public class FutureImpl implements Future, TransportStates {

	// state
	protected Object stateLock = new Object();
	protected int state = STATE_INITIAL;
	protected int finishState = FINISH_STATE_SUCCESS;
	protected Throwable cause;

	protected Object doneBytesLock = new Object();
	protected long doneBytes;

	protected Channel channel;
	protected Work work;

	protected List<FutureListener> listeners = new ArrayList<FutureListener>();

	public FutureImpl(Channel channel) {
		this.channel = channel;
	}

	public void setWork(Work work) {
		this.work = work;
	}

	@Override
	public Channel getChannel() {
		return channel;
	}

	@Override
	public long getDoneBytes() {
		synchronized (doneBytesLock) {
			return doneBytes;
		}
	}

	public boolean isStarted() {
		synchronized (stateLock) {
			return state >= STATE_STARTED;
		}
	}

	@Override
	public boolean isDone() {
		synchronized (stateLock) {
			return state >= STATE_STOPED;
		}
	}

	@Override
	public boolean isCancelled() {
		synchronized (stateLock) {
			return isDone() && finishState == FINISH_STATE_CACELLED;
		}
	}

	@Override
	public boolean isSuccess() {
		synchronized (stateLock) {
			return isDone() && finishState == FINISH_STATE_SUCCESS;
		}
	}

	@Override
	public boolean isFailed() {
		synchronized (stateLock) {
			return isDone() && finishState == FINISH_STATE_FAILED;
		}
	}

	@Override
	public Throwable getCause() {
		synchronized (stateLock) {
			return cause;
		}
	}

	@Override
	public boolean cancel() {
		synchronized (stateLock) {
			return work.cancel();
		}
	}

	@Override
	public void addListener(FutureListener listener) {
		synchronized (stateLock) {
			synchronized (listeners) {
				listeners.add(listener);
				if (isDone()) {
					listener.operationComplete(this);
				}
			}
		}
	}

	@Override
	public void removeListener(FutureListener listener) {
		synchronized (listeners) {
			listeners.remove(listener);
		}
	}
	
	public void fireFutureListener() {
		synchronized (stateLock) {
			if (isDone()) {
				synchronized (listeners) {
					for (FutureListener listener : listeners) {
						listener.operationComplete(this);
					}
				}
			}
		}
	}

	@Override
	public Future await() throws InterruptedException {
		synchronized (stateLock) {
			while (!isDone()) {
				stateLock.wait();
			}
		}
		return this;
	}

	@Override
	public Future awaitUninterruptibly() {
		synchronized (stateLock) {
			while (!isDone()) {
				try {
					stateLock.wait();
				} catch (InterruptedException e) {
					// DoNothing
				}
			}
		}
		return this;
	}

	@Override
	public boolean await(long timeoutMillis) throws InterruptedException {
		if (timeoutMillis <= 0) {
			await();
		} else {
			long start = System.currentTimeMillis();
			long remain = timeoutMillis;
			synchronized (stateLock) {
				while (remain > 0 && !isDone()) {
					stateLock.wait(remain);
					remain = timeoutMillis - (System.currentTimeMillis() - start);
				}
			}
		}
		return isDone();
	}

	@Override
	public boolean awaitUninterruptibly(long timeoutMillis) {
		if (timeoutMillis <= 0) {
			awaitUninterruptibly();
		} else {
			long start = System.currentTimeMillis();
			long remain = timeoutMillis;
			synchronized (stateLock) {
				while (remain > 0 && !isDone()) {
					try {
						stateLock.wait(remain);
					} catch (InterruptedException e) {
						// DoNothing
					}
					remain = timeoutMillis - (System.currentTimeMillis() - start);
				}
			}
		}
		return isDone();
	}

	public Object getStateLock() {
		return stateLock;
	}

	public Object getDoneBytesLock() {
		return doneBytesLock;
	}

	public void setState(int state) {
		this.state = state;
	}
	
	public void setFinishState(int finishState) {
		this.finishState = finishState;
	}
	
	public void setCause(Throwable cause) {
		this.cause = cause;
	}

	public void setDoneBytes(long doneBytes) {
		this.doneBytes = doneBytes;
	}
}
